package edu.thu.thss.yxy.sip;

import java.util.*;

import javax.sip.Dialog;
import javax.sip.address.Address;
import javax.sip.address.SipURI;
import javax.sip.address.URI;
import javax.sip.header.FromHeader;
import javax.sip.header.ToHeader;
import javax.sip.message.Request;

import edu.thu.thss.yxy.sip.event.*;
import edu.thu.thss.yxy.util.Logger;

/**
 * A representation of a Call. The Call class must only be created by users.
 * 
 * @author Andy Gao
 */
public class Call {
	/**
	 * Our class logger.
	 */
	private static final Logger logger = Logger.getLogger(Call.class);

	/**
	 * An identifier uniquely representing the call.
	 */
	private final String callID;

	/**
	 * The JAIN SIP dialog that has been created by the application for
	 * communication with this call peer.
	 */
	private Dialog jainSipDialog = null;

	private String remoteSdpDescription = null;

	/**
	 * While in its early state the dialog cannot provide us with its
	 * corresponding transaction as it is not yet created That's where the
	 * initialRequest field comes in.
	 */
	private Request initialRequest = null;

	/**
	 * A list of all listeners currently registered for <tt>CallStateEvent</tt>s
	 */
	private final List<CallStateListener> callListeners = new Vector<CallStateListener>();

	/**
	 * The state that this call is currently in.
	 */
	private CallState callState = CallState.CALL_INITIALIZATION;

	public Call() {
		// create the uid
//		this.callID = String.valueOf(System.currentTimeMillis())
//				+ String.valueOf(super.hashCode());
		this.callID = String.valueOf(super.hashCode());
	}

	/**
	 * Returns the id of the specified Call.
	 * 
	 * @return a String uniquely identifying the call.
	 */
	public String getCallID() {
		return callID;
	}

	/**
	 * Sets the JAIN SIP dialog that has been created by the application for
	 * communication with this call peer.
	 * 
	 * @param dialog
	 *            the JAIN SIP dialog that has been created by the application
	 *            for this call.
	 */
	public void setDialog(Dialog dialog) {
		this.jainSipDialog = dialog;
	}

	/**
	 * Returns the JAIN SIP dialog that has been created by the application for
	 * communication with this call peer.
	 * 
	 * @return the JAIN SIP dialog that has been created by the application for
	 *         communication with this call peer.
	 */
	public Dialog getDialog() {
		return jainSipDialog;
	}

	public String getRemoteSdpDescription() {
		return remoteSdpDescription;
	}

	public void setRemoteSdpDescription(String remoteSdpDescription) {
		this.remoteSdpDescription = remoteSdpDescription;
	}

	/**
	 * Returns the state that this call is currently in.
	 * 
	 * @return a reference to the <tt>CallState</tt> instance that the call is
	 *         currently in.
	 */
	public CallState getCallState() {
		return callState;
	}

	/**
	 * Sets the state of this call and fires a call change event notifying
	 * registered listeners for the change.
	 * 
	 * @param newState
	 *            a reference to the <tt>CallState</tt> instance that the call
	 *            is to enter.
	 */
	protected void setCallState(CallState newState) {
		CallState oldState = getCallState();

		if (oldState != newState) {
			this.callState = newState;

			fireCallStatusChangeEvent(oldState, newState);
		}
	}

	/**
	 * Adds a call change listener to this call so that it could receive events
	 * on new call peers, theme changes and others.
	 * 
	 * @param listener
	 *            the listener to register
	 */
	public void addCallStateListener(CallStateListener listener) {
		synchronized (callListeners) {
			if (!callListeners.contains(listener))
				this.callListeners.add(listener);
		}
	}

	/**
	 * Removes <tt>listener</tt> to this call so that it won't receive further
	 * <tt>CallStateEvent</tt>s.
	 * 
	 * @param listener
	 *            the listener to register
	 */
	public void removeCallStateListener(CallStateListener listener) {
		synchronized (callListeners) {
			this.callListeners.remove(listener);
		}
	}

	/**
	 * Creates a <tt>CallChangeEvent</tt> with this class as <tt>sourceCall</tt>
	 * , and the specified <tt>eventID</tt> and old and new values and
	 * dispatches it on all currently registered listeners.
	 * 
	 * @param oldState
	 *            the value of the call property that changed, before the event
	 *            had occurred.
	 * @param newState
	 *            the value of the call property that changed, after the event
	 *            has occurred.
	 */
	protected void fireCallStatusChangeEvent(CallState oldState, CallState newState) {
		CallStateEvent ccEvent = new CallStateEvent(this, oldState, newState);

		logger.debug("Dispatching a CallChange event to "
				+ callListeners.size() + " listeners. event is: "
				+ ccEvent.toString());

		Iterator<CallStateListener> listeners;
		synchronized (callListeners) {
			listeners = new ArrayList<CallStateListener>(callListeners)
					.iterator();
		}

		while (listeners.hasNext()) {
			CallStateListener listener = listeners.next();
			listener.callStateChanged(ccEvent);
		}
	}

	public String getAddress() {
		if (jainSipDialog.getState() != null)
			return jainSipDialog.getRemoteParty().getURI().toString();
		else {
			if (jainSipDialog.isServer()) {
				FromHeader fromHeader = (FromHeader) initialRequest
						.getHeader(FromHeader.NAME);
				return fromHeader.getAddress().getURI().toString();
			} else {
				ToHeader toHeader = (ToHeader) initialRequest
						.getHeader(ToHeader.NAME);
				return toHeader.getAddress().getURI().toString();
			}
		}
	}

	public String getRemoteName() {
		Address address;
		if (jainSipDialog.getState() != null)
			address = jainSipDialog.getRemoteParty();
		else {
			if (jainSipDialog.isServer()) {
				FromHeader fromHeader = (FromHeader) initialRequest
						.getHeader(FromHeader.NAME);
				address = fromHeader.getAddress();
			} else {
				ToHeader toHeader = (ToHeader) initialRequest
						.getHeader(ToHeader.NAME);
				address = toHeader.getAddress();
			}
		}
		String retVal = null;
		if (address.getDisplayName() != null
				&& address.getDisplayName().trim().length() > 0)
			retVal = address.getDisplayName();
		else {
			URI uri = address.getURI();
			if (uri.isSipURI())
				retVal = ((SipURI) uri).getUser();
		}
		return retVal == null ? "" : retVal;
	}

	void setInitialRequest(Request request) {
		this.initialRequest = request;
	}

	/**
	 * Compares the specified object with this call and returns true if it the
	 * specified object is an instance of a Call object and if the extending
	 * telephony protocol considers the calls represented by both objects to be
	 * the same.
	 * 
	 * @param obj
	 *            the call to compare this one with.
	 * @return true in case both objects are pertaining to the same call and
	 *         false otherwise.
	 */
	public boolean equals(Object obj) {
		if (obj == null || !(obj instanceof Call))
			return false;
		return (obj == this) || ((Call) obj).getCallID().equals(getCallID());
	}

	/**
	 * Returns a hash code value for this call.
	 * 
	 * @return a hash code value for this call.
	 */
	public int hashCode() {
		return getCallID().hashCode();
	}

	/**
	 * Returns a string textually representing this Call.
	 * 
	 * @return a string representation of the object.
	 */
	public String toString() {
		return "Call: id=" + getCallID();
	}

}
